/*
 * Copyright 2023, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

#include "accelerant.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <syslog.h>

#include <AutoDeleterOS.h>

accelerant_info *gInfo;

//	#pragma mark -

/*!	This is the common accelerant_info initializer. It is called by
    both, the first accelerant and all clones.
*/
static status_t
init_common(int device, bool isClone)
{
    // initialize global accelerant info structure

    gInfo = (accelerant_info *)malloc(sizeof(accelerant_info));
    MemoryDeleter infoDeleter(gInfo);
    if (gInfo == NULL)
        return B_NO_MEMORY;

    memset(gInfo, 0, sizeof(accelerant_info));

    gInfo->is_clone = isClone;
    gInfo->device = device;
    gInfo->current_mode = UINT32_MAX;

    // get basic info from driver

    area_id sharedArea;
    if (ioctl(device, LS7A2000_GET_PRIVATE_DATA, &sharedArea, sizeof(area_id)) != 0)
        return B_ERROR;

    AreaDeleter sharedDeleter(clone_area("ls7a2000 shared info",
                                         (void **)&gInfo->shared_info, B_ANY_ADDRESS,
                                         B_READ_AREA | B_WRITE_AREA, sharedArea));
    status_t status = gInfo->shared_info_area = sharedDeleter.Get();
    if (status < B_OK)
        return status;

    // check that the version of the driver and accelerant match
    if (gInfo->shared_info->magic != LS7A2000_PRIVATE_DATA_MAGIC)
    {
        TRACE("MAGIC mismatch, driver magic: %#08x, accelerant magic: %#08x\n",
			gInfo->shared_info->magic, LS7A2000_PRIVATE_DATA_MAGIC));
        return B_ERROR;
    }

    // create mode list area
    status = create_mode_list();
    if (status != B_OK)
        return status;

    infoDeleter.Detach();
    sharedDeleter.Detach();
    return B_OK;
}

/*!	Cleans up everything done by a successful init_common(). */
static void
uninit_common(void)
{
    delete_area(gInfo->mode_list_area);
    gInfo->mode_list_area = -1;
    gInfo->mode_list = NULL;

    delete_area(gInfo->shared_info_area);
    gInfo->shared_info_area = -1;
    gInfo->shared_info = NULL;

    // close the file handle ONLY if we're the clone
    // (this is what Be tells us ;)
    if (gInfo->is_clone)
        close(gInfo->device);

    free(gInfo);
}

//	#pragma mark - public accelerant functions

/*!	Init primary accelerant */
status_t
ls7a2000_init_accelerant(int device)
{
    TRACE(("ls7a2000_init_accelerant()\n"));

    status_t status = init_common(device, false);
    if (status != B_OK)
        return status;

    return B_OK;
}

/*!	Clone accelerant */
status_t
ls7a2000_clone_accelerant(void *data)
{
    TRACE(("ls7a2000_clone_accelerant()\n"));

    // clone info from the master accelerant
    int32 size = *((int32 *)data);
    if (size != sizeof(accelerant_info))
        return B_BAD_VALUE;

    int device = *((int *)(data + sizeof(int32)));
    return init_common(device, true);
}

/*!	Get size of the accelerant clone info */
status_t
ls7a2000_accelerant_clone_info_size(void)
{
    // clone the accelerant
    return sizeof(accelerant_info);
}

/*!	Get info about the accelerant clone */
status_t
ls7a2000_get_accelerant_clone_info(void *data)
{
    TRACE(("ls7a2000_get_accelerant_clone_info()\n"));

    memcpy(data, &gInfo->device, sizeof(int));
    return B_OK;
}

/*!	Free accelerant */
void ls7a2000_uninit_accelerant(void)
{
    TRACE(("ls7a2000_uninit_accelerant()\n"));
    uninit_common();
}

/*!	Get accelerant device info */
status_t
ls7a2000_get_accelerant_device_info(accelerant_device_info *info)
{
    TRACE(("ls7a2000_get_accelerant_device_info()\n"));

    info->version = B_ACCELERANT_VERSION;
    strcpy(info->name, "LS7A2000");
    strcpy(info->chipset, "LS7A2000");
    strcpy(info->serial_no, "None");
    info->memory = gInfo->shared_info->graphics_memory_size;
    info->dac_speed = gInfo->shared_info->pll_max_freq;

    return B_OK;
}

/*!	Get frame buffer config */
status_t
ls7a2000_get_frame_buffer_config(frame_buffer_config *config)
{
    TRACE(("ls7a2000_get_frame_buffer_config()\n"));

    // Get frame buffer config from shared info
    config->frame_buffer = gInfo->shared_info->frame_buffer;
    config->frame_buffer_dma = gInfo->shared_info->physical_frame_buffer;
    config->bytes_per_row = gInfo->shared_info->bytes_per_row;

    return B_OK;
}

/*!	Get the retrace semaphore */
sem_id
ls7a2000_accelerant_retrace_semaphore(void)
{
    TRACE(("ls7a2000_accelerant_retrace_semaphore()\n"));
    return gInfo->shared_info->vertical_blanking_sem;
}